import { BaseService } from '@/core/base.service';
import { BaseQueryDto, NoticeListDto } from '@/dtos';
import { InjectQueue } from '@nestjs/bull';
import { Injectable } from '@nestjs/common';
import { Notice, Prisma } from '@prisma/client';
import { Queue } from 'bull';
import dayjs from 'dayjs';

@Injectable()
export class NoticeService extends BaseService {
  constructor(
    @InjectQueue('notice')
    private readonly noticeQueue: Queue,
  ) {
    super();
  }

  async getList(userId: number, { type, params, sort }: NoticeListDto) {
    const { current = 1, pageSize = 20, ...where } = params;

    let sql = `
      SELECT #{fields} FROM Notice AS t
      ${
        type === -1
          ? `
          JOIN UserFollowNotice AS ufn 
          ON ufn.noticeId = t.id 
            AND ufn.userId = ${userId}
          `
          : ''
      }
      WHERE t.isDeleted = false
        AND t.isEnabled = true
        ${!!where.keyword ? `AND t.title LIKE '%${where.keyword}%'` : ''}
        ${!!type && type > 0 ? `AND t.type = ${type}` : ''}
        ${!!type && type === -1 ? `AND ufn.noticeId = t.id` : ''}
    `;

    const [{ count }] = await this.prisma.$queryRawUnsafe<[{ count: number }]>(
      sql.replace('#{fields}', 'COUNT(t.id) AS count'),
    );

    const list = await this.prisma.$queryRawUnsafe<Notice[]>(
      `${sql.replace(
        '#{fields}',
        `
        t.id, t.title, t.type, t.image, t.publishAt, t.desc,
        (
          SELECT COUNT(1)
          FROM UserFollowNotice AS ufn 
          WHERE ufn.noticeId = t.id 
            AND ufn.userId = ${userId}
        ) AS isFollow
      `,
      )}
      ORDER BY ${
        !!sort && Object.keys(sort).length > 0
          ? Object.entries(sort)
              .map(([k, v]) => `t.${k} ${v === 'descend' ? 'DESC' : 'ASC'}`)
              .join(',')
          : 't.publishAt DESC'
      }
      LIMIT ${pageSize} 
      OFFSET ${(current - 1) * pageSize}
      `,
    );

    const total = count ? Number(count) : 0;
    const totalPage = Math.ceil(total / pageSize);
    const hasMore = current < totalPage;

    const popupId = await this.redis.get('notice_popup');

    const aRead = [];
    for (const item of list) {
      const is_read = await this.redis.sismember(
        `notice_read:${item.id}`,
        userId,
      );
      if (is_read) {
        aRead[item.id] = 1;
      } else {
        aRead[item.id] = 0;
      }
    }

    return {
      total,
      totalPage,
      hasMore,
      current,
      pageSize,
      list: list.map((o: any) => ({
        ...o,
        isFollow: Number(o.isFollow) > 0,
        isPopup: popupId == o.id ? 1 : 0,
        isRead: aRead[o.id],
      })),
    };
  }

  async query({ params, sort }: BaseQueryDto<Prisma.NoticeWhereUniqueInput>) {
    const { current = 1, pageSize = 20, keyword, ...where } = params;

    let sql = `
      SELECT #{fields} FROM Notice AS t
      WHERE t.isDeleted = false
        ${!!where.title ? `AND t.title LIKE '%${where.title}%'` : ''}
    `;

    const [{ count }] = await this.prisma.$queryRawUnsafe<[{ count: number }]>(
      sql.replace('#{fields}', 'COUNT(t.id) AS count'),
    );

    const list = await this.prisma.$queryRawUnsafe<Notice[]>(
      `${sql.replace(
        '#{fields}',
        't.id, t.title, t.type, t.image, t.content, t.publishAt, t.sort, t.isEnabled, t.isAutoPublish',
      )}
      ORDER BY ${
        !!sort && Object.keys(sort).length > 0
          ? Object.entries(sort)
              .map(([k, v]) => `t.${k} ${v === 'descend' ? 'DESC' : 'ASC'}`)
              .join(',')
          : 't.createdAt DESC'
      }
      LIMIT ${pageSize} 
      OFFSET ${(current - 1) * pageSize}
      `,
    );

    const total = count ? Number(count) : 0;
    const totalPage = Math.ceil(total / pageSize);
    const hasMore = current < totalPage;

    const popupId = await this.redis.get('notice_popup');

    return {
      total,
      totalPage,
      hasMore,
      current,
      pageSize,
      list: list.map((o: any) => ({
        ...o,
        popup: popupId == o.id ? 1 : 0,
      })),
    };
  }

  async findOne(
    where: Prisma.NoticeWhereUniqueInput,
    select?: Prisma.NoticeSelect,
  ): Promise<Notice | null> {
    return this.prisma.notice.findUnique({
      where,
      select,
    });
  }

  async create(data: Prisma.NoticeCreateInput): Promise<Notice> {
    const notice = await this.prisma.notice.create({
      data: {
        ...data,
        ...(data.publishAt
          ? { publishAt: dayjs(data.publishAt.toString()).toDate() }
          : {}),
      },
    });
    if (
      !notice.isEnabled &&
      notice.isAutoPublish &&
      notice.publishAt &&
      dayjs().isBefore(notice.publishAt)
    ) {
      const job = await this.noticeQueue.add(
        'publish',
        {
          id: notice.id,
        },
        {
          delay: dayjs(notice.publishAt.toString()).diff(dayjs()),
        },
      );
      await this.update({
        where: {
          id: notice.id,
        },
        data: {
          jobId: job.id.toString(),
        },
      });
    }

    return notice;
  }

  async update(params: {
    where: Prisma.NoticeWhereUniqueInput;
    data: Prisma.NoticeUpdateInput;
  }): Promise<Notice> {
    const { where, data } = params;
    if (
      !data.isEnabled &&
      data.isAutoPublish &&
      data.publishAt &&
      dayjs().isBefore(data.publishAt.toString())
    ) {
      const job = await this.noticeQueue.add(
        'publish',
        {
          id: where.id,
        },
        {
          delay: dayjs(data.publishAt.toString()).diff(dayjs()),
        },
      );
      data.jobId = job.id.toString();
    }

    return this.prisma.notice.update({
      where,
      data: {
        ...data,
        ...(data.publishAt
          ? { publishAt: dayjs(data.publishAt.toString()).toDate() }
          : {}),
      },
    });
  }

  async remove(where: Prisma.NoticeWhereUniqueInput): Promise<Notice> {
    return this.prisma.notice.update({
      where,
      data: {
        isDeleted: true,
      },
    });
  }

  async popup(id) {
    const isOpen = await this.redis.get('notice_popup');
    if (isOpen == id) {
      return await this.redis.del('notice_popup');
    } else {
      return await this.redis.set('notice_popup', id);
    }
  }

  async read(id, uid) {
    return await this.redis.sadd(`notice_read:${id}`, uid);
  }
}
